import type { PluginOption } from 'vite';

import type { NitroMockPluginOptions } from '../typing';

import { colors, consola, getPackage } from '@croco/node-utils';

import getPort from 'get-port';
import { build, createDevServer, createNitro, prepare } from 'nitropack';

const hmrKeyRe = /^runtimeConfig\.|routeRules\./;

export const viteNitroMockPlugin = ({ mockServerPackage = '@croco/backend-mock', port = 5320, verbose = true }: NitroMockPluginOptions = {}): PluginOption => {
    return {
        async configureServer(server) {
            const availablePort = await getPort({ port });
            if (availablePort !== port) {
                return;
            }

            const pkg = await getPackage(mockServerPackage);
            if (!pkg) {
                consola.log(`Package ${mockServerPackage} not found. Skip mock server.`);
                return;
            }

            runNitroServer(pkg.dir, port, verbose);

            const _printUrls = server.printUrls;
            server.printUrls = () => {
                _printUrls();

                consola.log(`  ${colors.green('➜')}  ${colors.bold('Nitro Mock Server')}: ${colors.cyan(`http://localhost:${port}/api`)}`);
            };
        },
        enforce: 'pre',
        name: 'vite:mock-server',
    };
};

async function runNitroServer(rootDir: string, port: number, verbose: boolean) {
    let nitro: any;
    const reload = async () => {
        if (nitro) {
            consola.info('Restarting dev server...');
            if ('unwatch' in nitro.options._c12) {
                await nitro.options._c12.unwatch();
            }
            await nitro.close();
        }
        nitro = await createNitro(
            {
                dev: true,
                preset: 'nitro-dev',
                rootDir,
            },
            {
                c12: {
                    async onUpdate({ getDiff, newConfig }) {
                        const diff = getDiff();
                        if (diff.length === 0) {
                            return;
                        }
                        verbose && consola.info(`Nitro config updated:\n${diff.map((entry) => `  ${entry.toString()}`).join('\n')}`);
                        await (diff.every((e) => hmrKeyRe.test(e.key)) ? nitro.updateConfig(newConfig.config) : reload());
                    },
                },
                watch: true,
            },
        );
        nitro.hooks.hookOnce('restart', reload);

        const server = createDevServer(nitro);
        await server.listen(port, { showURL: false });
        await prepare(nitro);
        await build(nitro);

        if (verbose) {
            console.log('');
            consola.success(colors.bold(colors.green('Nitro Mock Server started.')));
        }
    };
    return await reload();
}
